using System.Diagnostics;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Ruminate.DataStructures;
using Ruminate.GUI.Content;

namespace Ruminate.GUI.Framework {

    /// <summary>
    /// 
    /// </summary>
    internal class ElementManager {        

        /// <summary>
        /// The top most element in the GUI. Always the DudElement.
        /// </summary>
        internal TreeNode<Element> RootElement { get; set; }

        /// <summary>
        /// Information that is specific to the current instance of the RuminateGUI class. 
        /// Every element within a RuminateGUI instance has a reference to the same instance 
        /// of InstanceData.
        /// </summary>
        private InstanceData InstanceData { get; set; }

        /*####################################################################*/
        /*                          External Input                            */
        /*####################################################################*/

        internal CharEnteredHandler CharEntered { get; set; }
        internal KeyEventHandler KeyDown { get; set; }
        internal KeyEventHandler KeyUp { get; set; }
        internal MouseEventHandler MouseClick { get; set; }
        internal MouseEventHandler MouseDoubleClick { get; set; }
        internal MouseEventHandler MouseDown { get; set; }
        internal MouseEventHandler MouseHover { get; set; }
        internal MouseEventHandler MouseMove { get; set; }
        internal MouseEventHandler MouseUp { get; set; }
        internal MouseEventHandler MouseWheel { get; set; }

        /*####################################################################*/
        /*                           Initialization                           */
        /*####################################################################*/

        /// <summary>
        /// Create a new element manager (should only be one per RuminateGUI Instance).
        /// </summary>
        /// <param name="instanceData">The instance of InstanceData to share across this implementation.</param>
        internal ElementManager(InstanceData instanceData) {

            InstanceData = instanceData;

            var width = InstanceData.GraphicsDevice.Viewport.Width;
            var height = InstanceData.GraphicsDevice.Viewport.Height;

            RootElement = new TreeNode<Element>(new RootElement(new Offset(0, 0, width, height), InstanceData, this));
            RootElement.Data.TreeNode = RootElement;
            RootElement.Data.InstanceData = InstanceData;
            RootElement.Data.LocationComponent.AlignToViewport(InstanceData.GraphicsDevice);            
        }        

        /*####################################################################*/
        /*                          Tree Management                           */
        /*####################################################################*/
        
        #region Tree Data Structure Management

        internal void AddElement(Element element) {

            RootElement.Data.AddChild(element);
        }

        internal void RemoveElement(Element element) {

            RootElement.Data.RemoveChild(element);
        }       

        internal static void InitTree(TreeNode<Element> node) {

            node.Data.InternalInit(node.Parent.Data.InstanceData);
            node.Data.LocationComponent.AlignToParentElement();
            node.Data.Render();

            foreach (var child in node.Children) {
                InitTree(child);
            }
        }

        #endregion

        /*####################################################################*/
        /*                          Update Elements                           */
        /*####################################################################*/

        #region Update Elements

        internal void UpdateElements() {

            if (RootElement.Data.Active && RootElement.Data.Visible) {
                foreach (var child in RootElement.Children) {
                    UpdateElementsDfs(child);
                }
            }
        }

        private static void UpdateElementsDfs(TreeNode<Element> node) {

            node.Data.Update();

            if (node.Data.Active && node.Data.Visible) {
                foreach (var child in node.Children) {
                    UpdateElementsDfs(child);
                }
            }
        }              
         
        #endregion

        /*####################################################################*/
        /*                              Drawing                               */
        /*####################################################################*/
        
        #region Drawing

        internal void Draw() {            

            InstanceData.GraphicsDevice.SamplerStates[0] = SamplerState.PointClamp;
            InstanceData.GraphicsDevice.RasterizerState = InstanceData.RasterizerState;

            if (RootElement.Data.Visible) {
                foreach (var child in RootElement.Children) {
                    if (child.Data.Visible) {
                        DrawElementsDfs(child);
                    }
                }
            }

            InstanceData.GraphicsDevice.ScissorRectangle 
                = InstanceData.GraphicsDevice.Viewport.Bounds;            
        }

        private void DrawElementsDfs(TreeNode<Element> node) {

            node.Data.LocationComponent.AlignToParentElement();

            InstanceData.GraphicsDevice.ScissorRectangle
                = node.Parent.Data.LocationComponent.RenderSafeArea;

            node.Data.Draw();
            node.Data.LocationComponent.DrawDebug();

            foreach (var child in node.Children) {
                if (child.Data.Visible) {
                    DrawElementsDfs(child);
                }
            }
        }

        #endregion

        /*####################################################################*/
        /*                           Debug Drawing                            */
        /*####################################################################*/

        #region Debug Drawing

        [Conditional("INPUT")]
        private void DrawClickAreas() {

            if (RootElement.Data.Active && RootElement.Data.Visible) {
                foreach (var child in RootElement.Children) {
                    DrawClickAreasDfs(child);
                }
            }
        }

        private static void DrawClickAreasDfs(TreeNode<Element> node) {

            foreach (var child in node.Children) {
                if (child.Data.Active && child.Data.Visible) {
                    DrawClickAreasDfs(child);
                }
            }
        }

        #endregion
    }
}
